org.springframework.beans包遵循JavaBeans提供的标准。一个JavaBean是一个简单的类,有一个无参的构造函数,遵循一个命名约定(举例来说)名为bingoMadness的属性将具有setter方法setBingoMadness(..)和getter方法getBingoMadness()。更多关于JavaBeans的规范,请查看Oracle的网站(javabeans)。
beans包里一个相当重要的类是BeanWrapper接口和它的实现(BeanWrapperImpl)。根据Java文档,BeanWraper提供了(单独或者批量的)设置和获取属性值的功能,获取属性的描述,或者查询属性是否可读或者可写的。同时,BeanWrapper提供了对嵌套的属性的支持,可以无限深度的设置属性的子属性。BeanWrapper提供了添加JavaBeans的PropertyChangeListenersVetoableChangeListeners的能力,而不需要在目标类中添加其他代码。最后,BeanWrapper提供了设置索引属性的支持。BeanWrapper通常不会在应用程序代码中直接被使用,而是通过DataBinderBeanFacotry
BeanWrapper的工作方式就像它名字描述的一样:它对Bean进行包装,然后像Bean一样工作,比如设置和读取属性。

Setting and getting basic and nested properties

设置和读取属性可以通过setPropertyValues(s)getPropertyValue(s)方法,每个方法都有一系列的重载的变体。Spring Java文档提供了更详细的描述。重要的是要知道几个用于指示对象属性的约定。几个例子:

表8.1 Example of properties Expression | Explanation ---|--- name | 指明name属性根据getName()isName()setName() account.name | 指明account嵌套的属性name,像getAccount().setName()或者getAccout().getName() account[2]|指明account中的第三个属性。有序的属性可以通过array,list或是其他自然的有序集合表示。 account[COMPANYNAME]|指明account下的Map形式的属性,以COMPANYNAME作为键去搜索。

下面你会发现BeanWrapper设置和获取属性的一些例子。
(如果你不打算直接使用BeanWrapper,下面的章节对你来说并不十分重要。如果你仅仅是使用DataBinderBeanFactory和其开放的实现,你可以跳过关于PropertyEditors的章节。)
考虑以下两个例子:

public class Company {

    private String name;
    private Employee managingDirector;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Employee getManagingDirector() {
        return this.managingDirector;
    }

    public void setManagingDirector(Employee managingDirector) {
        this.managingDirector = managingDirector;
    }
}
public class Employee {

    private String name;

    private float salary;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getSalary() {
        return salary;
    }

    public void setSalary(float salary) {
        this.salary = salary;
    }
}

下列代码展示了如何检索和操作CompaniesEmployees属性:

BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

8.4.2 Built-in PropertyEditor implementations

Spring通过使用PropertyEditors的属性来实现ObjectString之间的转换。如果你仔细想想,有时用其他方式表示属性而不是对象本事是很方便的。比如,Date可以以一种可读的方式表现(作为String``2017-14-09),然而我们仍需要从可读的形式转换为对象(甚至:需要将任意人类可读的形式转换成Date)。这可以通过注册自定义的java.beans.PropertyEditor来实现。为BeanWrapper或是在前几章提到的特定的IoC容器注册自定义的编辑器,使它们知道如何将属性转成期待的类型。更多有关PropertryEditors的信息请查看Oracle提供的关于java.beans的文档。
以下是Spring中使用属性编辑的例子: 当bean创建完的时候通过PropertyEditors来设置属性。当一个属性在XML文件中声明的值是以java.lang.String的形式存在时,(如果它的set方法有Class属性)Spring将用ClassEditor来尝试解析Class 在Spring MVC中,解析HTTP请求的参数是通过各种PropertyEditors完成的,你可以在CommnadController的所有子类中手动绑定PropertyEditor
Spring有一系列内置的PropertyEditors让这一切变得简单。这些都列在下面,它们都在org.springframework.beans.propertyeditors包中。大部分,并非全部(下列已经指明),都默认的被注册到BeanWrapperImpl。当然你也可以注册你自己的属性编辑器让它以某种方式工作:

表8.2 Built-in PropertyEditors

说明
ByteArrayPropertyEditor 自己数组的编辑器。字符串可以很容易的被转成对应的字节。被BeanWrapperImpl默认注册
ClassEditor 将字符串解析成实际的类,反之亦然。没有找到对应的类时,会抛出IllegalArgumentException。被BeanWrapperImpl默认注册。
CustomBooleanEditor 布尔值的可定制的属性编辑器。默认被BeanWrapperImpl注册,但是,可以被自定义的编辑器实例覆盖。
CustomCollectionEditor 容器类的属性编辑器,将任意表示Collecion的类型转换成给定的Collection类型。
CustomDateEditor 可定制的java.util.Date属性编辑器,支持自定义的日期格式。没有被默认注册,需要使用者根据需要注册合适的格式。
CustomNumberEditor 任意Number子类像IntegerLong,Float,Double的可定制的编辑器。默认被BeanWrapperImpl注册,但是可以注册自己的实例来覆盖它。
FileEditor 能够将字符串解析成java.io.File对象。默认被BeanWrapperImpl注册。
InputStreamEditor 单向属性编辑器,能够接受一个文本字符串并生成(通过中间ResourceEditorResource)一个InputStream,所以InputStream属性可以直接设置为字符串。注意默认的使用不会为你关闭InputStream!默认被BeanWrapperImpl注册
LocaleEditor 能够将字符串解析成Locale对象,反之亦然(字符串的格式是[country][variant],这和Locale的toString()方法相同)。默认被BeanWrapperImpl注册
PatternEditor 能够将字符串解析成java.util.regex.Pattern对象,反之亦然。
PrppertiesEditor 能够将(根据java.util.Properties类的文档定义的形式的)字符串解析成Properties对象。默认被BeanWrapperImpl的注册。
StringTrimmerEditor 修剪字符串的编辑器,允许将空字符串转换成null。未被默认注册,需要根据需要自行注册。
URLEditor 可以将代表URL的字符串解析成真实的URL对象。默认被BeanWrapperImpl注册。

Spring通过使用PropertyEditorManager来设置可能需要的属性编辑器的搜索路径。搜索的路径也包括了sun.bean.editors,它包括了对FontColor类型以及大部分基本类型的PropertyEditor实现。需要注意的是,标准的JavaBeans基础结构将会自动的发现Property类(如果它们与它处理的类位于同一个包中,并且与该类拥有相同的名称,只是附加了"Editor",则不需要显示注册);例如,可以有以下类和包结构,这足以使得FooEditor类被标识并作为Foo类型属性的PropertyEditor
com
 └chank
  └pop
   ├Foo
   └FooEditor // the PropertyEditor for the Foo class
请注意,您也可以在这里使用标准的BeanInfo JavaBeans机制(在这里描述的不是令人惊讶的细节)。 查找下面的示例,使用BeanInfo机制显式注册一个或多个PropertyEditor实例和关联类的属性。
com
 └chank
  └pop
   ├Foo
   └FooEditor // the BeanInfo for the Foo class
这里是FooBeanInfo类的源码。它讲CustomNumberEditorFoo类的age属性相关联。

public class FooBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}

Registering additional custom PropertyEditors

当bean属性设置为字符串时,Spring容器最终将会用标准的JavaBeans属性编辑器将字符串转成属性的复杂类型。Spring预注册了一系列定制的属性编辑器(例如,将一个字符串表示的类名转换成真是的Class对象)。另外,标准的JavaBeans属性编辑器查找机制允许一个被恰当命名且位于同一个包中的PropertyEditor类被自动的找到。
如果有需要注册其他定制的PropertyEditors,有几个办法是可行的。大部分手动的方法是,假设你有一个BeanFactory的引用,就可以用ConfiguraleBeanFactory接口的registerCustomEditor()方法,这通常都不是便利的,因此也不推荐。另一个稍微渐变的机制是使用一个叫CustomEditorConfigurer的特使的后置处理器。尽管Bean Factory的后置处理器可以和Bean Factory的实现一起使用,但是CustomEditorConfigurer具有嵌套的属性设置,因此强烈建议将其与Application Context一起使用,可以和其它bean以相同的方式发布,会被自动的检测和应用。
注意,所有的Bean Factory或是Application Context都会自动使用一系列内置的属性编辑器,通过使用BeanWrapper来处理属性转换。BeanWrapper注册的标注的属性编辑器已经在之前的小节里列出。另外,ApplicationContext还会额外的添加或覆盖一些编辑器以适应特定的应用程序上下文类的方式处理查找资源。
标准的JavaBeans的PropertyEditor实例被用来将传递的字符串属性值转换成实际的复杂的属性类型。CustomEditorConfigurer,一个Bean Factory的后置处理器,可以很方便的被用来给一个Application Context添加PropertyEditor实例。
考虑一个用户的类ExoticType,和另一个需要ExoticType作为属性的DependsOnExoticType类:

package example;

public class ExoticType {

    private String name;

    public ExoticType(String name) {
        this.name = name;
    }
}

public class DependsOnExoticType {

    private ExoticType type;

    public void setType(ExoticType type) {
        this.type = type;
    }
}

当设置属性的时候,我们希望将属性当作字符串,然后,PropertyAEditor会将其转换成实际的ExoticType实例:

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

PropertyEditor的实现像这样:

// converts string representation to ExoticType object
package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

最终,我们用CustomEditorConfigurer去注册新的PropertyEditorApplicationContext,使得它像希望的那样工作:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>

Using PropertyEditorRegistrars

另一个注册属性编辑器到Spring容器的机制是创建和使用PropertyEditorRegistrar。当你希望在不同的场景里注册一系列属性编辑器时,这个接口是很有效的:写一个对应的registrar,并在各种情况下重复使用。
PropertyEditorRegistarsPropertyEditorRegistry的接口一起工作。PropertyEditorRegistry被Spring的BeanWrapper(和DataBinder)实现。PropertyEditorRegistrarsCustomEditorConfigurer(在这里介绍)结合使用时特别方便,它提供了一个名为setPropertyEditorRegistaras(...)的方法:以这种方式添加到的CustomEditorConfigurerPropertyEditorRegistrars可以很容易的被DataBinder和Spring MVC的Controller共享。另外,它也避免了同步自定义的编辑器:一个PropertyEditorRegistrar为每个需要的Bean创建全新的属性编辑器实例。
最好的例子就是去使用PropertyEditorRegistrar。首先,你需要创建一个你自己的PropertyEditorRegistrar实例:

package com.foo.editors.spring;

public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {

    public void registerCustomEditors(PropertyEditorRegistry registry) {

        // it is expected that new PropertyEditor instances are created
        registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());

        // you could register as many custom property editors as are required here...
    }
}

也可以参考org.springfrawork.beans.support.ResourceEditorRegistrar作为PropertyEditorRegistrar实现的例子。注意在它registerCustomEditors(..)的实现中是如何创建每个属性编辑器的实例的。
然后,我们配置CustomEditorConfigurer,将CustomPropertyEditorRegistrar注入:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar"
    class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

最后,可能有些超出本章的重点,对于那些使用Spring MVC框架的人来说,使用PropertyEditorRegistrars来为Controller(比如SimpleFormController)绑定数据非常方便。下面的例子在initBinder(..)方法中用到了PropertyEditorRegistrar

public final class RegisterUserController extends SimpleFormController {

    private final PropertyEditorRegistrar customPropertyEditorRegistrar;

    public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
        this.customPropertyEditorRegistrar = propertyEditorRegistrar;
    }

    protected void initBinder(HttpServletRequest request,
            ServletRequestDataBinder binder) throws Exception {
        this.customPropertyEditorRegistrar.registerCustomEditors(binder);
    }

    // other methods to do with registering a User
}

这种注册PropertyEditor的风格可以让代码简洁(initBinder(..)实现只有一行!),并且允许将常用的PropertyEditor注册代码封装到一个类里,被许多需要的Controllers共享。